package cc.z8g.browser.core.tab

import android.app.Activity
import android.graphics.Bitmap
import cc.z8g.browser.core.BrowserContract
import cc.z8g.browser.core.tab.bundle.BundleStore
import cc.z8g.browser.utils.sp.UserSp
import cc.z8g.browser.utils.ThreadPool
import cc.z8g.browser.utils.isFileUrl
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Maybe
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.subjects.BehaviorSubject
import io.reactivex.rxjava3.subjects.PublishSubject
import java.util.concurrent.Callable

/**
 * 实现了 [BrowserContract.Model] 接口的标签存储库。负责管理标签列表的状态，并向其中添加新标签或从中移除标签。
 */
class TabsRepository(
    private val activity: Activity,
    private val incognitoMode: Boolean,
    private val tabPager: TabPager,
    private val bundleStore: BundleStore,
    private val recentTabModel: RecentTabModel,
    private val initialUrl: String?,
    private val permissionInitializerFactory: PermissionCreator.Factory,
    iconFreeze: Bitmap,
    tabWebChromeClientProvider: Callable<TabWebChromeClient>,
) : BrowserContract.Model {
    private val tabFactory: TabFactory = TabFactory(activity, incognitoMode, iconFreeze, tabWebChromeClientProvider)

    private var isInitialized = BehaviorSubject.createDefault(false)
    private var selectedTab: TabModel? = null
    private val tabsListObservable = PublishSubject.create<List<TabModel>>()

    private fun afterInitialization(): Single<Boolean> =
        isInitialized.filter { it }.firstOrError()

    override fun deleteTab(id: Int): Completable = Completable.fromAction {
        if (selectedTab?.id == id) {
            tabPager.clearTab()
        }
        val tab = tabsList.forId(id)
        recentTabModel.addClosedTab(tab.freeze())
        tab.destroy()
        tabsList = tabsList - tab
    }.doOnComplete {
        tabsListObservable.onNext(tabsList)
    }.subscribeOn(ThreadPool.getMainScheduler())

    override fun deleteAllTabs(): Completable =
        afterInitialization().flatMapCompletable {
            Completable.fromAction {
                tabPager.clearTab()

                tabsList.forEach(TabModel::destroy)
                tabsList = emptyList()
            }
        }.doOnComplete {
            tabsListObservable.onNext(tabsList)
        }.subscribeOn(ThreadPool.getMainScheduler())

    override fun createTab(tabCreator: TabCreator): Single<TabModel> =
        afterInitialization().flatMap { createTabUnsafe(tabCreator) }
            .subscribeOn(ThreadPool.getMainScheduler())

    /**
     * Creates a tab without waiting for the browser to be initialized.
     */
    private fun createTabUnsafe(tabCreator: TabCreator): Single<TabModel> =
        Single.fromCallable {
            val webView = WebViewFactory.createWebView(activity, incognitoMode)
            tabPager.addTab(webView)
            val tabAdapter = tabFactory.constructTab(tabCreator, webView)

            tabsList = tabsList + tabAdapter

            return@fromCallable tabAdapter
        }.doOnSuccess {
            tabsListObservable.onNext(tabsList)
        }.subscribeOn(ThreadPool.getMainScheduler())

    override fun reopenTab(): Maybe<TabModel> = Maybe.fromCallable(recentTabModel::lastClosed)
        .flatMapSingle { createTab(BundleCreator(it)) }
        .subscribeOn(ThreadPool.getMainScheduler())

    override fun selectTab(id: Int): TabModel {
        val selected = tabsList.forId(id)
        selectedTab = selected
        tabPager.selectTab(id)

        return selected
    }

    override var tabsList = emptyList<TabModel>()
        private set

    override fun tabsListChanges(): Observable<List<TabModel>> = tabsListObservable.hide()

    override fun initializeTabs(): Maybe<List<TabModel>> =
        Single.fromCallable(bundleStore::retrieve)
            .subscribeOn(ThreadPool.getDiskScheduler())
            .observeOn(ThreadPool.getMainScheduler())
            .flatMapObservable { Observable.fromIterable(it) }
            .concatWith(Maybe.fromCallable { initialUrl }.map {
                if (it.isFileUrl()) {
                    permissionInitializerFactory.create(it)
                } else {
                    UrlCreator(it)
                }
            })
            .flatMapSingle(::createTabUnsafe)
            .toList()
            .filter(List<TabModel>::isNotEmpty)
            .doAfterTerminate {
                isInitialized.onNext(true)
            }

    override fun freeze() {
        if (UserSp.getBoolean(UserSp.KEY_RESTORE_LOST_TABS)) {
            bundleStore.save(tabsList)
        }
    }

    override fun clean() {
        bundleStore.deleteAll()
    }

    private fun List<TabModel>.forId(id: Int): TabModel = requireNotNull(find { it.id == id })
}
